[アップデート]AWS Organizationsの各種リソースをCloudFormationで管理できるようになりました
AWS OrganizationsがCloudFormation(以下CFn)をサポートしました。
以下リソースをCFnで管理できるようになりました。
- AWSアカウント
- organizational units (OUs)
- ポリシー
- 4種類あります。代表的なのはSCPですね。
- SCP (Service control policy)
- Artificial Intelligence (AI) services opt-out policy
- Backup policy
- Tag policy
- 4種類あります。代表的なのはSCPですね。
それぞれ使ってみます。
Organization作成
最初にそもそものOraganizationsを作成する必要があります。この部分はCFn未対応です。なので、以下ページを参考にマネジメントコンソールかCLIで作成しましょう。お使いのアカウントがOraganizationのマネジメントアカウントになります。
※ TerraformだとこのOrganization作成部分もIaC化できます。
ルート直下にアカウントを作成する
以下テンプレートでCFnスタックを作成してみます。Oraganizationのルート直下に1つアカウントを作成します。ルート直下なのでParentIds
プロパティにはr-
から始まるルートIDを設定します。(後から知ったのですが、ルート直下にアカウントを作成する場合はこのParentIds
プロパティは省略可能だそうです)
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: - r-xxxx
made-with-cfn
アカウントが無事作成できました。(他に以前作成したOUがあります。)
複数AWSアカウントの同時作成する
アカウント作成に使うCFnリソースAWS::Organizations::Account
のドキュメントに以下記述があります。
If you include multiple accounts in a single template, you must use the DependsOn attribute on each account resource type so that the accounts are created sequentially. If you create multiple accounts at the same time, Organizations returns an error and the stack operation fails.
複数アカウントを同時に作成するとエラーになるよ、だからDependsOn
を使って順次作成してね、とのことです。本当にエラーになるのか、以下のようにDependsOn
を使わずに2アカウント追加してみました。
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: - r-xxxx + MultipleCreation1: + Type: AWS::Organizations::Account + Properties: + AccountName: made-with-cfn2 + Email: sample+made-with-cfn2@gmail.com + ParentIds: + - r-xxxx + MultipleCreation2: + Type: AWS::Organizations::Account + Properties: + AccountName: made-with-cfn3 + Email: sample+made-with-cfn3@gmail.com + ParentIds: + - r-xxxx
予想に反してこのスタック更新は成功しました。CreateAccount APIのリファレンスには
Using CreateAccount to create multiple temporary accounts isn't recommended.
と「おすすめしない」とだけ書かれていたので、必ず複数同時作成が失敗するわけでも無いようですね。とはいえ本番運用する際には念の為DependsOn
句を書いておいた方が良さそうです。
OUを作成する
OUに関してはParentId
プロパティは必須項目で、省略はできません。
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: - r-xxxx MultipleCreation1: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn2 Email: sample+made-with-cfn2@gmail.com ParentIds: - r-xxxx MultipleCreation2: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn3 Email: sample+made-with-cfn3@gmail.com ParentIds: - r-xxxx + TestOu: + Type: AWS::Organizations::OrganizationalUnit + Properties: + Name: test-ou + ParentId: r-xxxx
作成成功しました。
OU配下にアカウントを移動する
OUリソースの論理名をRef
するとOUのIDが取得できます。
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: + - !Ref TestOu - - r-xxxx MultipleCreation1: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn2 Email: sample+made-with-cfn2@gmail.com ParentIds: - r-xxxx MultipleCreation2: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn3 Email: sample+made-with-cfn3@gmail.com ParentIds: - r-xxxx TestOu: Type: AWS::Organizations::OrganizationalUnit Properties: Name: test-ou ParentId: r-xxxx
SCPを作成する
前述の通りポリシーは4種類ありますが、ここでは一番良く使うであろうSCPを作成してみたいと思います。以下ドキュメントを参考に、特定のタグを付与していない場合はEC2インスタンス作成が失敗するようにしてみます。
AWS::Organizations::Account
のRefを使ってみたかったので、ターゲットはOraganization全体でもOUでもなくアカウントにしています。
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: - !Ref TestOu MultipleCreation1: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn2 Email: sample+made-with-cfn2@gmail.com ParentIds: - r-xxxx MultipleCreation2: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn3 Email: sample+made-with-cfn3@gmail.com ParentIds: - r-xxxx TestOu: Type: AWS::Organizations::OrganizationalUnit Properties: Name: test-ou ParentId: r-xxxx + TestSCP: + Type: AWS::Organizations::Policy + Properties: + Type: SERVICE_CONTROL_POLICY + TargetIds: + - !Ref TestAccountMadeWithCfn + Name: test-scp + Description: Require project tag to create EC2 instances + Content: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DenyRunInstanceWithNoProjectTag", + "Effect": "Deny", + "Action": "ec2:RunInstances", + "Resource": [ + "arn:aws:ec2:*:*:instance/*", + "arn:aws:ec2:*:*:volume/*" + ], + "Condition": { + "Null": { + "aws:RequestTag/Project": "true" + } + } + } + ] + }
こちらのテンプレートでスタック更新したところ、以下エラーになりました。
Resource handler returned message: "This operation can be performed only for enabled policy types. (Service: Organizations, Status Code: 400, Request ID: xxxx)" (RequestToken: yyyy, HandlerErrorCode: InvalidRequest)
調べたところ、ポリシーは4種類それぞれオプトインする必要があるんですね。Organizationsのコンソールで確認したところ、どれも有効化されていませんでした。
SCPを有効化して、再度スタック更新を実行したところ成功しました。Organizationsコンソールでポリシーが作成できていることも確認できました。 ターゲットもアカウント1つだけです。
SCPをテストしてみます。このアカウントにログインしてEC2インスタンスをProjectタグ無しで作成してみました。
狙い通りエラーになりました。 エラーメッセージがエンコードされていて内容がわかりません。以下ドキュメントを参考にエラーメッセージをデコードしてみました。
aws sts decode-authorization-message --encoded-message (コンソールからエンコードメッセージをコピペ) | jq .DecodedMessage --raw-output | jq .
{ "allowed": false, "explicitDeny": true, "matchedStatements": { "items": [ { "statementId": "DenyRunInstanceWithNoProjectTag", "effect": "DENY", "principals": { "items": [ { "value": "AROAWWZIGWOTQSBVXYNIX" } ] }, "principalGroups": { "items": [] }, "actions": { "items": [ { "value": "ec2:RunInstances" } ] }, "resources": { "items": [ { "value": "arn:aws:ec2:*:*:instance/*" }, { "value": "arn:aws:ec2:*:*:volume/*" } ] }, "conditions": { "items": [ { "key": "aws:RequestTag/Project", "values": { "items": [ { "value": "true" } ] } } ] } } ] }, "failures": { "items": [] }, "context": { "principal": { "id": "AROAWWZIGWOTQSBVXYNIX:xxxxxuser", "arn": "arn:aws:sts::012345678901:assumed-role/AWSReservedSSO_AdministratorAccess_132ab909823e04ea/xxxxxuser" }, "action": "ec2:RunInstances", "resource": "arn:aws:ec2:ap-northeast-1:012345678901:instance/*", "conditions": { "items": [ { "key": "ec2:InstanceMarketType", "values": { "items": [ { "value": "on-demand" } ] } }, { "key": "aws:Resource", "values": { "items": [ { "value": "instance/*" } ] } }, { "key": "aws:Account", "values": { "items": [ { "value": "012345678901" } ] } }, { "key": "ec2:AvailabilityZone", "values": { "items": [ { "value": "ap-northeast-1c" } ] } }, { "key": "ec2:ebsOptimized", "values": { "items": [ { "value": "false" } ] } }, { "key": "ec2:IsLaunchTemplateResource", "values": { "items": [ { "value": "false" } ] } }, { "key": "ec2:InstanceType", "values": { "items": [ { "value": "t2.micro" } ] } }, { "key": "ec2:RootDeviceType", "values": { "items": [ { "value": "ebs" } ] } }, { "key": "aws:Region", "values": { "items": [ { "value": "ap-northeast-1" } ] } }, { "key": "aws:Service", "values": { "items": [ { "value": "ec2" } ] } }, { "key": "ec2:InstanceID", "values": { "items": [ { "value": "*" } ] } }, { "key": "aws:Type", "values": { "items": [ { "value": "instance" } ] } }, { "key": "ec2:Tenancy", "values": { "items": [ { "value": "default" } ] } }, { "key": "ec2:Region", "values": { "items": [ { "value": "ap-northeast-1" } ] } }, { "key": "aws:ARN", "values": { "items": [ { "value": "arn:aws:ec2:ap-northeast-1:012345678901:instance/*" } ] } } ] } } }
設定したSCPで弾かれていることがわかりますね。
では今度はタグを付けて作成してみます。SCPに"value": "arn:aws:ec2:*:*:volume/*"
も書いていたのでボリュームにもタグ付けが必要です。
今度は成功しました!
アカウントを削除する
以下のようにAWS::Organizations::Account
リソースをテンプレートから削除して更新しても、アカウントは削除されずただCFn管理下から外れるだけです。
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: - !Ref TestOu MultipleCreation1: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn2 Email: sample+made-with-cfn2@gmail.com ParentIds: - r-xxxx - MultipleCreation2: - Type: AWS::Organizations::Account - Properties: - AccountName: made-with-cfn3 - Email: sample+made-with-cfn3@gmail.com - ParentIds: - - r-xxxx TestOu: Type: AWS::Organizations::OrganizationalUnit Properties: Name: test-ou ParentId: r-xxxx TestSCP: Type: AWS::Organizations::Policy Properties: Type: SERVICE_CONTROL_POLICY TargetIds: - !Ref TestAccountMadeWithCfn Name: test-scp Description: Require project tag to create EC2 instances Content: | { "Version": "2012-10-17", "Statement": [ { "Sid": "DenyRunInstanceWithNoProjectTag", "Effect": "Deny", "Action": "ec2:RunInstances", "Resource": [ "arn:aws:ec2:*:*:instance/*", "arn:aws:ec2:*:*:volume/*" ], "Condition": { "Null": { "aws:RequestTag/Project": "true" } } } ] }
これはデフォルトのDeletionPolicy
値がRetain
だからです。アカウントを削除したい場合はDeletionPolicy
を明示的に指定する必要があります。
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: - !Ref TestOu MultipleCreation1: Type: AWS::Organizations::Account + DeletionPolicy: Delete Properties: AccountName: made-with-cfn2 Email: sample+made-with-cfn2@gmail.com ParentIds: - r-xxxx TestOu: Type: AWS::Organizations::OrganizationalUnit Properties: Name: test-ou ParentId: r-xxxx TestSCP: Type: AWS::Organizations::Policy Properties: Type: SERVICE_CONTROL_POLICY TargetIds: - !Ref TestAccountMadeWithCfn Name: test-scp Description: Require project tag to create EC2 instances Content: | { "Version": "2012-10-17", "Statement": [ { "Sid": "DenyRunInstanceWithNoProjectTag", "Effect": "Deny", "Action": "ec2:RunInstances", "Resource": [ "arn:aws:ec2:*:*:instance/*", "arn:aws:ec2:*:*:volume/*" ], "Condition": { "Null": { "aws:RequestTag/Project": "true" } } } ] }
ですが、この更新はエラーになりました。エラーメッセージは以下です。
The submitted information didn't contain changes. Submit different information to create a change set.
DeletionPolicy
追加だけでは差分として認識してくれないんですね…ぐぬぬ
以下エントリを参考にタグ追加も加えます。
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: - !Ref TestOu MultipleCreation1: Type: AWS::Organizations::Account + DeletionPolicy: Delete Properties: AccountName: made-with-cfn2 Email: sample+made-with-cfn2@gmail.com ParentIds: - r-xxxx + Tags: + - Key: WillBeDeleted + Value: True TestOu: Type: AWS::Organizations::OrganizationalUnit Properties: Name: test-ou ParentId: r-xxxx TestSCP: Type: AWS::Organizations::Policy Properties: Type: SERVICE_CONTROL_POLICY TargetIds: - !Ref TestAccountMadeWithCfn Name: test-scp Description: Require project tag to create EC2 instances Content: | { "Version": "2012-10-17", "Statement": [ { "Sid": "DenyRunInstanceWithNoProjectTag", "Effect": "Deny", "Action": "ec2:RunInstances", "Resource": [ "arn:aws:ec2:*:*:instance/*", "arn:aws:ec2:*:*:volume/*" ], "Condition": { "Null": { "aws:RequestTag/Project": "true" } } } ] }
今度はスタック更新成功しました。
あとは該当AWS::Organizations::Account
リソースを削除して再度スタック更新です。
AWSTemplateFormatVersion: 2010-09-09 Description: AWS Organizations supports CloudFormations Resources: TestAccountMadeWithCfn: Type: AWS::Organizations::Account Properties: AccountName: made-with-cfn Email: sample+made-with-cfn@gmail.com ParentIds: - !Ref TestOu - MultipleCreation1: - Type: AWS::Organizations::Account - DeletionPolicy: Delete - Properties: - AccountName: made-with-cfn2 - Email: sample+made-with-cfn2@gmail.com - ParentIds: - - r-xxxx - Tags: - - Key: WillBeDeleted - Value: True TestOu: Type: AWS::Organizations::OrganizationalUnit Properties: Name: test-ou ParentId: r-xxxx TestSCP: Type: AWS::Organizations::Policy Properties: Type: SERVICE_CONTROL_POLICY TargetIds: - !Ref TestAccountMadeWithCfn Name: test-scp Description: Require project tag to create EC2 instances Content: | { "Version": "2012-10-17", "Statement": [ { "Sid": "DenyRunInstanceWithNoProjectTag", "Effect": "Deny", "Action": "ec2:RunInstances", "Resource": [ "arn:aws:ec2:*:*:instance/*", "arn:aws:ec2:*:*:volume/*" ], "Condition": { "Null": { "aws:RequestTag/Project": "true" } } } ] }
アカウントは即削除されるわけではないのです。90日の猶予期間があります。この点については以下のエントリをご参照ください。
感想
だいぶ昔にTerraformでOrganizationsまわりを作成してみたことがあったので、「え、まだCFnはサポートしてなかったのか」というのが正直な感想でした。とはいえIaCの選択肢が増えるのは嬉しいですね。OUやSCPはマルチアカウントでAWSを使う際にとても影響範囲の広い重要なリソースですので、IaCで変更履歴を管理するのは重要だと思います。